﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using NeoPatterns.Core.Registry;
using NeoPatterns.Core.Registry.Implementation;
using NeoPatterns.Exception;
using NeoPatterns.Utility;

namespace NeoPatterns.Patterns.IoC
{
    public static class AttributeConfiguration
    {
        public static IObjectRegistry ObjectRegistry
        {
            get
            {
                if (Application.ObjectRegistry == null)
                {
                    Application.ObjectRegistry = new ObjectRegistry();
                    Load();
                }
                return Application.ObjectRegistry;
            }
        }

        public static void Load()
        {
            LoadAssemblies();
            ParseAssemblies();
        }

        private static void LoadAssemblies()
        {
            // this domain is used only to get information about other assemblies
            var appDomain = AppDomain.CreateDomain("NeoPatterns.Attributes.$$$");
            try
            {
                string directory = Path.GetDirectoryName(new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath);
                foreach (var file in Directory.GetFiles(directory))
                {
                    try
                    {
                        var assembly = Assembly.LoadFrom(file);
                        // if the assembly is not already loaded in current domain
                        if (!(from a in AppDomain.CurrentDomain.GetAssemblies() where a.FullName == assembly.FullName select a).Any())
                        {
                            // and it contains the attribute
                            if (assembly.GetCustomAttributes(typeof(ObjectsAssemblyAttribute), true).Length > 0)
                            {
                                // then add it to the current domain
                                Assembly.LoadFrom(file);
                            }
                        }
                    }
                    catch (BadImageFormatException)
                    {
                    }
                }
            }
            finally
            {
                AppDomain.Unload(appDomain);
            }
        }

        /// <summary>
        /// Avoids common .NET assemblies, since they're not supposed to contain NeoPatterns objects
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        private static bool Avoid(Assembly assembly)
        {
            if (typeof(object).Assembly == assembly) // corlib
                return true;
            if (typeof(Uri).Assembly == assembly) // corlib
                return true;
            if (typeof(Action).Assembly == assembly) // corlib
                return true;
            if (assembly.GetName().Name.StartsWith("System.", StringComparison.Ordinal))
                return true;
            return false;
        }

        /// <summary>
        /// Parses assemblies for object definitions
        /// </summary>
        private static void ParseAssemblies()
        {
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (Avoid(assembly))
                    continue;
                foreach (var type in assembly.GetTypes())
                {
                    var objectAttribute = type.GetCustomAttribute<ObjectAttribute>(true);
                    if (objectAttribute != null)
                    {
                        Action<object, IObjectRegistry> initializer = Initializer;
                        ObjectRegistry.RegisterDefinition(objectAttribute.Name, type, null, objectAttribute.Instance,
                                                       null, initializer);
                    }
                }
            }
        }

        /// <summary>
        /// This initializer is called by NeoPatterns for each given instance.
        /// It scans for properties/fiels with InjectDependencyAttribute, looks for the optional name
        /// and resolves the dependency
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="objectRegistry"></param>
        public static void Initializer(object obj, IObjectRegistry objectRegistry)
        {
            foreach (var memberInfo in obj.GetType().GetMembers())
            {
                var diAttribute = memberInfo.GetCustomAttribute<InjectDependencyAttribute>(true);
                if (diAttribute != null)
                {
                    var memberType = memberInfo.GetMemberType();
                    object dependency;
                    // we have two options:
                    // 1. A dependency name, we use it
                    if (diAttribute.DependencyName != null)
                        dependency = objectRegistry.Get(diAttribute.DependencyName);
                        // 2. No name, we make a lookup by dependency type
                    else
                    {
                        var names = objectRegistry.GetNamesForType(memberType);
                        if (names.Count != 1)
                            throw new TypeAmbiguityException();
                        dependency = objectRegistry.Get(names[0]);
                    }
                    // injection
                    memberInfo.SetMemberValue(obj, dependency);
                }
            }
        }
    }
}